﻿using System;
using System.Activities;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow.Design;

namespace TfsCloudDeploy.Workflow.Core.Activities
{
    /// <summary>
    /// Returns a list of assemblies that has been modified based on comparing them to a baseline.
    /// </summary>
    [Designer(typeof(TeamBuildBaseActivityDesigner)), SRDescription(typeof(ExtendedWorkflowResources), "GetModifiedAssembliesActivityDescription"), BuildActivity(HostEnvironmentOption.All)]
    public class GetModifiedAssemblies : CodeActivity<string[]>
    {
        [SRDescription(typeof(ExtendedWorkflowResources), "BaselineAssembliesFolderDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), RequiredArgument]
        public InArgument<string> BaselineAssembliesFolder { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "BuiltAssembliesDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), RequiredArgument]
        public InArgument<string[]> BuiltAssemblies { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "SkipRemoveTempFolderDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public InArgument<bool> SkipRemoveTempFolder { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "ShowVerboseLogOutputDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public InArgument<bool> ShowVerboseLogOutput { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "IgnoreAssemblyVersionChangeDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), DefaultValue(true)]
        public InArgument<bool> IgnoreAssemblyVersionChange { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "IgnoreAssemblyFileVersionChangeDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), DefaultValue(true)]
        public InArgument<bool> IgnoreAssemblyFileVersionChange { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "ToolPathDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public InArgument<string> ToolPath { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "BinariesDirectoryDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), RequiredArgument]
        public InArgument<string> BinariesDirectory { get; set; }

        protected override string[] Execute(CodeActivityContext context)
        {
            string[] builtAssemblies = this.BuiltAssemblies.Get(context);
            if (builtAssemblies.Length == 0)
            {
                WorkflowUtility.CreateBuildMessage("[GetModifiedAssemblies]: A list of built assemblies must be passed.", context, BuildMessageImportance.High);
                throw new ArgumentException("[GetModifiedAssemblies]: BuiltAssemblies list was not provided for comparison.", "BuiltAssemblies");
            }

            string baselineAssembliesFolder = this.BaselineAssembliesFolder.Get(context);
            if (string.IsNullOrWhiteSpace(baselineAssembliesFolder))
            {
                WorkflowUtility.CreateBuildMessage("[GetModifiedAssemblies]: A baseline folder containing checked-in assemblies to compare to must be passed.", context, BuildMessageImportance.High);
                throw new ArgumentException("[GetModifiedAssemblies]: BaselineAssembliesFolder was not provided.", "BaselineAssembliesFolder");
            }

            IBuildDetail buildDetail = context.GetExtension<IBuildDetail>();
            string dropLocation = buildDetail.DropLocation;
            string binariesDirectory = this.BinariesDirectory.Get(context);

            Tasks.GetModifiedAssemblies getModifiedAssembliesTask = new Tasks.GetModifiedAssemblies();
            getModifiedAssembliesTask.BuildEngine = new BuildEngine(context);
            getModifiedAssembliesTask.BuiltAssemblies = GetTaskItemsFromStringArray(builtAssemblies, dropLocation, binariesDirectory);
            getModifiedAssembliesTask.BaselineAssembliesFolder = baselineAssembliesFolder;
            getModifiedAssembliesTask.ShowVerboseLogOutput = this.ShowVerboseLogOutput.Get(context);
            getModifiedAssembliesTask.SkipRemoveTempFolder = this.SkipRemoveTempFolder.Get(context);
            getModifiedAssembliesTask.IgnoreAssemblyFileVersionChange = this.IgnoreAssemblyFileVersionChange.Get(context);
            getModifiedAssembliesTask.IgnoreAssemblyVersionChange = this.IgnoreAssemblyVersionChange.Get(context);
            getModifiedAssembliesTask.ToolPath = this.ToolPath.Get(context);

            if (getModifiedAssembliesTask.Execute())
            {
                ITaskItem[] modifiedAssemblies = getModifiedAssembliesTask.ModifiedAssemblies;
                List<string> modifiedAssembliesList = GetFilesToCheckinFromTaskItems(builtAssemblies, modifiedAssemblies, dropLocation, binariesDirectory);

                WorkflowUtility.CreateBuildMessage(string.Format("[GetModifiedAssemblies]: {0} out of {1} artifacts modified.", modifiedAssemblies.Length, getModifiedAssembliesTask.BuiltAssemblies.Length), context);

                return modifiedAssembliesList.ToArray();
            }
            else
            {
                WorkflowUtility.CreateBuildMessage("[GetModifiedAssemblies]: Task execution failed.", context, BuildMessageImportance.High);
                throw new Exception("[GetModifiedAssemblies]: Task execution failed.");
            }
        }

        private ITaskItem[] GetTaskItemsFromStringArray(string[] stringArray, 
                                                        string dropLocation,
                                                        string binariesDirectory)
        {
            List<ITaskItem> taskItemList = new List<ITaskItem>();
            Array.ForEach(stringArray, s =>
                {
                    string[] srcDstArray = s.Split(new char[] { '|' }, StringSplitOptions.None);
                    if (srcDstArray.Length > 1)
                    {
                        // TODO: Need to store the sanitized list and link it to source item in order to reuse it in the
                        //       IsBuiltAssemblyModified method and not have to re-evaluate the following expression.
                        string builtAssembly = GetSanitizedSourceFile(srcDstArray[0], dropLocation, binariesDirectory);
                        taskItemList.Add(new TaskItem(builtAssembly));
                    }
                });

            return taskItemList.ToArray();
        }

        private List<string> GetFilesToCheckinFromTaskItems(string[] builtAssemblies, 
                                                            ITaskItem[] modifiedArtifacts,
                                                            string dropLocation,
                                                            string binariesDirectory)
        {
            List<string> filteredBuiltAssemblies = new List<string>();
            Array.ForEach(builtAssemblies, s =>
                {
                    if (IsBuiltAssemblyModified(s, modifiedArtifacts, dropLocation, binariesDirectory))
                    {
                        filteredBuiltAssemblies.Add(s);
                    }
                });
            return filteredBuiltAssemblies;
        }

        private bool IsBuiltAssemblyModified(string builtAssemblySrcDst, 
                                             ITaskItem[] modifiedArtifacts, 
                                             string dropLocation,
                                             string binariesDirectory)
        {
            string[] srcDstArray = builtAssemblySrcDst.Split(new char[] { '|' }, StringSplitOptions.None);
            if (srcDstArray.Length > 1)
            {
                string builtAssembly = GetSanitizedSourceFile(srcDstArray[0], dropLocation, binariesDirectory);
                foreach (ITaskItem modifiedArtifact in modifiedArtifacts)
                {
                    if (
                        string.Compare(modifiedArtifact.ItemSpec, builtAssembly,
                                       StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private string GetSanitizedSourceFile(string unsanitizedSourceFile,
                                              string dropLocation,
                                              string binariesDirectory)
        {
            return
                Path.Combine(
                        unsanitizedSourceFile.Trim().StartsWith("[DropLocation]\\", StringComparison.InvariantCultureIgnoreCase) ? dropLocation : binariesDirectory,
                        unsanitizedSourceFile.Trim().StartsWith("[DropLocation]\\", StringComparison.InvariantCultureIgnoreCase) ? unsanitizedSourceFile.Trim().Replace("[DropLocation]\\", string.Empty) : unsanitizedSourceFile.Trim());
        }
    }
}
